#pragma once

#include "common.hpp"
#include "log.hpp"

#include <utility>

/**
 * @brief the representation of HTTP message
 */
struct http_message {
    std::map<std::string, std::vector<std::string>> key_values;

    static auto parse_http_message(std::istream& input) -> std::variant<std::string, http_message>;

    [[nodiscard]] bool contains_key_value(const std::string& key, const std::string& value) const;

    [[nodiscard]] bool contains_key(const std::string& key) const {
        return key_values.find(key) != key_values.end();
    }

    [[nodiscard]] std::string dump() const;
};

struct handshake_request {

    std::string method;
    std::string uri;
    std::string version;

    http_message message;

    static auto parse_handshake_request(nonstd::span<char>) -> std::variant<std::string, handshake_request>;

    [[nodiscard]] bool is_valid_request() const;

    [[nodiscard]] std::string dump() const {
        std::stringstream ss;
        ss << method << " " << uri << " " << version << "\r\n" << message.dump() << "\r\n";
        return ss.str();
    }
};

struct handshake_response {
    std::string version;
    std::string status;
    std::string reason;

    http_message message;

    static auto parse_handshake_response(nonstd::span<char> buf) -> std::variant<std::string, handshake_response>;

    [[nodiscard]] bool is_valid_response(const std::string& expected_accept) const {
        if (version != "HTTP/1.1") return false;
        if (status != "101") return false;
        if (reason != "Switching Protocols") return false;
        if (!message.contains_key_value("Upgrade", "websocket")) return false;
        if (!message.contains_key_value("Connection", "Upgrade")) return false;
        if (!message.contains_key_value("Sec-WebSocket-Accept", expected_accept)) return false;
        return true;
    }

    [[nodiscard]] std::string dump() const {
        std::stringstream ss;
        ss << version << " " << status << " " << reason << "\r\n" << message.dump() << "\r\n";
        return ss.str();
    }
};

enum websocket_frame_code : int {
    CONTINUATION_FRAME = 0,
    TEXT_FRAME = 1,
    BINARY_FRAME = 2,
    CONNECTION_CLOSE = 8,
    PING = 9,
    PONG = 10
};

enum class websocket_state { CONNECTING, OPEN, CLOSING, CLOSED };

namespace nonstd {
inline std::string to_string(websocket_frame_code code) {
    switch (code) {
    case CONTINUATION_FRAME: return "continuation";
    case TEXT_FRAME: return "text";
    case BINARY_FRAME: return "binary";
    case CONNECTION_CLOSE: return "close";
    case PING: return "ping";
    case PONG: return "pong";
    default: return "invalid websocket frame opcode";
    }
}
} // namespace nonstd

/**
 * @brief the representation of a websocket frame
 */
struct websocket_frame {
    bool FIN{true}, RSV1{false}, RSV2{false}, RSV3{false};
    bool masked{false};
    websocket_frame_code opcode{CONTINUATION_FRAME};
    uint64_t length{0};
    std::array<char, 4> mask_key{0, 0, 0, 0};
    std::vector<char> buffer;

    websocket_frame() = default;

    websocket_frame(const websocket_frame&) = default;
    websocket_frame& operator=(const websocket_frame&) = default;

    websocket_frame(websocket_frame&& frame) noexcept;
    websocket_frame& operator=(websocket_frame&& frame) noexcept;

    ~websocket_frame() = default;

    [[nodiscard]] bool is_control_frame() const {
        return opcode > 7;
    }

    /**
     * @brief dump the binary form of this frame to a buffer
     *
     * @return std::vector<char> the dumped content
     */
    [[nodiscard]] std::vector<char> dump() const;

    [[nodiscard]] std::vector<char> dump_header() const;

    /**
     * @brief parse a websocket frame from a char buffer
     *
     * @param buf a borrowed char buffer
     * @return std::variant<std::string, size_t, std::pair<websocket_frame, size_t>>
     * std::string -> exception
     * size_t -> bytes required to reach next stage
     * std::pair<websocket_frame, size_t> -> parse result , parsed size
     */
    static auto parse_websocket_frame(nonstd::span<char> buf)
        -> std::variant<std::string, size_t, std::pair<websocket_frame, size_t>>;

    /**
     * @brief parse several websocket frame from a char buffer
     *
     * @param buf a borrowed char buffer
     * @return std::variant<std::string, std::tuple<std::vector<websocket_frame>, size_t, size_t>>
     * std::string -> exception
     * std::tuple<
     *   std::vector<websocket_frame>, -> parse result
     *   size_t, -> parsed size
     *   size_t  -> bytes required to reach next stage
     * >
     */
    static auto parse_multiple_websocket_frame(nonstd::span<char> buf)
        -> std::variant<std::string, std::tuple<std::vector<websocket_frame>, size_t, size_t>>;

    std::string description(size_t limit = 100) {
        if (!FIN || opcode == CONTINUATION_FRAME) {
            lwarn("not complete websocket frame");
        }
        std::string result = "type: " + nonstd::to_string(opcode) + "\n";
        result += "length: " + std::to_string(length) + "\n";
        result += "data sample: ";
        if (opcode == TEXT_FRAME) {
            result += std::string(buffer.data(), std::min(limit, static_cast<size_t>(length)));
        } else {
            std::stringstream ss;
            for (size_t i = 0; i < std::min(limit, static_cast<size_t>(length)); i++) {
                ss << std::hex << std::setfill('0') << std::setw(2) << (0xff & static_cast<int>(buffer[i])) << " ";
            }
            result += ss.str();
        }
        return result;
    }
};

/**
 * @brief the received content handler for websocket
 */
class websocket_receive_buffer {
public:
    websocket_receive_buffer(std::function<bool(websocket_frame)> receive_hook, bool is_server)
        : _is_server(is_server)
        , _receive_hook(std::move(receive_hook)) {
    }

    /**
     * @brief to handle some received data
     *
     * @param buffer the view of the data
     * @return true no error occurs
     * @return false the connection should close
     */
    [[nodiscard]] bool receive_data(std::vector<char> buffer);

private:
    std::mutex _m;

    size_t _bytes_required{0};

    bool _is_server;
    std::function<bool(websocket_frame)> _receive_hook;
    std::vector<char> _buffer;
    std::vector<websocket_frame> _unhandled_frames;
};